#include "qae_qansibuffer.h"
#include "qae_qansiline.h"
#include <QtCore/QString>
#include <QtCore/QRegExp>
#include <QtCore/QRect>
#include <QtCore/QTextCodec>
#include <QtCore/QDebug>


namespace QAnsiEditor {


QAnsiBuffer::QAnsiBuffer(int iRow, int iColumn)
{
    m_iRow = iRow;
    m_iColumn = iColumn;
    for (int i = 0; i < m_iRow; i++) {
        m_ansiLineList.append(new QAnsiLine);
    }

    m_iCursorX = 0;
    m_iCursorY = 0;
    m_iSavedCursorX = 0;
    m_iSavedCursorY = 0;
    m_iHackingCursorX = 0;
    m_iHackingCursorY = 0;
    m_chCurrentColor = DEFAULT_COLOR;
    m_chCurrentEffect = DEFAULT_EFFECT;
    m_chHackingColor = DEFAULT_COLOR;
    m_chHackingEffect = DEFAULT_EFFECT;
    m_bRect = true;
    m_ptSelStart = QPoint(-1, -1);
    m_ptSelEnd = QPoint(-1, -1);
    m_rcSel = QRect();
}

QAnsiBuffer::~QAnsiBuffer()
{
    for (int i = 0; i < m_iRow; i++) {
        delete m_ansiLineList[i];
    }
    m_ansiLineList.clear();
}

int QAnsiBuffer::getRowCount() const
{
    return m_iRow;
}

int QAnsiBuffer::getColumnCount() const
{
    return m_iColumn;
}

QAnsiLine *QAnsiBuffer::at(int iIndex) const
{
    return m_ansiLineList.at(iIndex);
}

void QAnsiBuffer::appendTextContent(const QString &strText)
{
    QAnsiLine *line =  m_ansiLineList.at(m_iCursorY);
    // line is certainly valid here
    line->setCurrentColor(m_chCurrentColor);
    line->setCurrentEffect(m_chCurrentEffect);
    int iLength = QAnsiLine(strText).getLength();
    line->replaceText(strText, m_iCursorX, iLength);
    moveCursor(iLength, 0);
}

int QAnsiBuffer::getCursorX() const
{
    return m_iCursorX;
}

int QAnsiBuffer::getCursorY() const
{
    return m_iCursorY;
}

void QAnsiBuffer::setCursor(int iX, int iY)
{
    m_ansiLineList.at(m_iCursorY)->setChanges(m_iCursorX, m_iCursorX+1);  // assume 2

    m_iCursorX = iX;
    m_iCursorY = iY;

    if (m_iCursorX < 0) {
        m_iCursorX = 0;
    }
    if (m_iCursorX > m_iColumn - 1) {
        m_iCursorX = m_iColumn - 1;
    }

    if (m_iCursorY < 0) {
        m_iCursorY = 0;
    }
    if (m_iCursorY > m_iRow - 1) {
        m_iCursorY = m_iRow - 1;
    }
}

void QAnsiBuffer::moveCursor(int iOffsetX, int iOffsetY)
{
    setCursor(m_iCursorX+iOffsetX, m_iCursorY+iOffsetY);
}

void QAnsiBuffer::saveCursor()
{
    m_iSavedCursorX = m_iCursorX;
    m_iSavedCursorY = m_iCursorY;
}

void QAnsiBuffer::restoreCursor()
{
    setCursor(m_iSavedCursorX, m_iSavedCursorY);
}

void QAnsiBuffer::saveAttribute()
{
    m_iHackingCursorX = m_iCursorX;
    m_iHackingCursorY = m_iCursorY;
    m_chHackingColor = m_chCurrentColor;
    m_chHackingEffect = m_chCurrentEffect;
}

void QAnsiBuffer::restoreAttribute()
{
    QAnsiLine *line =  m_ansiLineList.at(m_iHackingCursorY);
    // line is certainly valid here
    line->setColor(m_iHackingCursorX, m_chHackingColor);
    line->setEffect(m_iHackingCursorX, m_chHackingEffect);
}

char QAnsiBuffer::getCurrentColor() const
{
    return m_chCurrentColor;
}

void QAnsiBuffer::setCurrentColor(char chColor)
{
    m_chCurrentColor = chColor;
}

char QAnsiBuffer::getCurrentEffect() const
{
    return m_chCurrentEffect;
}

void QAnsiBuffer::setCurrentEffect(char chEffect)
{
    m_chCurrentEffect = chEffect;
}

void QAnsiBuffer::eraseToLineBegin()
{
    clearArea(0, m_iCursorY, m_iCursorX, 1);
}

void QAnsiBuffer::eraseToLineEnd()
{
    clearArea(m_iCursorX, m_iCursorY, -1, 1);
}

void QAnsiBuffer::eraseEntireLine()
{
    clearArea(0, m_iCursorY, -1, 1);
}

void QAnsiBuffer::eraseToScreenBegin()
{
    clearArea(0, m_iCursorY, m_iCursorX, 1);
    clearArea(0, 0, -1, m_iCursorY - 1);
}

void QAnsiBuffer::eraseToScreenEnd()
{
    clearArea(m_iCursorX, m_iCursorY, -1, 1);
    clearArea(0, m_iCursorY + 1, -1, m_iRow - 1 - m_iCursorY);
}

void QAnsiBuffer::eraseEntireScreen()
{
    clearArea(0, 0, m_iColumn, m_iRow);
    setCursor(0, 0);
}

void QAnsiBuffer::deleteLine(int iCount)
{
    int y = m_iRow - m_iCursorY;
    if (iCount >= y) {
        clearArea(0, m_iCursorY, -1, y);
    } else {
        scrollLines(m_iCursorY, iCount);
    }
}

void QAnsiBuffer::insertLine(int iCount)
{
    int y = m_iRow - m_iCursorY;
    if (iCount >= y) {
        clearArea(0, m_iCursorY, -1, y);
    } else {
        scrollLines(m_iCursorY, -iCount);
    }
}

bool QAnsiBuffer::getSelection(QPoint *ppt1, QPoint *ppt2)
{
    Q_ASSERT(ppt1 != 0 && ppt2 != 0);
    *ppt1 = m_ptSelStart;
    *ppt2 = m_ptSelEnd;
    return m_ptSelStart!=QPoint(-1, -1) && m_ptSelEnd!=QPoint(-1, -1);
}

bool QAnsiBuffer::setSelection(const QPoint &pt1, const QPoint &pt2)
{
    Q_ASSERT(pt1.x() >= 0 && pt1.x() < m_iColumn);
    Q_ASSERT(pt1.y() >= 0 && pt1.y() < m_iRow);
    Q_ASSERT(pt2.x() >= 0 && pt2.x() < m_iColumn);
    Q_ASSERT(pt2.y() >= 0 && pt2.y() < m_iRow);

    if (pt2 == m_ptSelEnd) {
        return false;
    }

    if (m_bRect) {
        if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
            for (int i = qMin(pt1.y(), pt2.y()); i <= qMax(pt1.y(), pt2.y()); i++) {
                m_ansiLineList.at(i)->setChanges(qMin(pt1.x(), pt2.x()), qMax(pt1.x(), pt2.x()));
            }
        } else {
            int minY = qMin(pt1.y(), pt2.y());
            int maxY = qMax(pt1.y(), pt2.y());
            for (int i = qMin(minY, m_ptSelEnd.y()); i <= qMax(maxY, m_ptSelEnd.y()); i++) {
                m_ansiLineList.at(i)->setChanges(qMin(pt2.x(), m_ptSelEnd.x()), qMax(pt2.x(), m_ptSelEnd.x()));
            }
            if (pt2.x() >= pt1.x()) {
                for (int i = qMin(pt2.y(), m_ptSelEnd.y()); i <= qMax(pt2.y(), m_ptSelEnd.y()); i++) {
                    m_ansiLineList.at(i)->setChanges(pt1.x(), qMax(pt2.x(), m_ptSelEnd.x()));
                }
            } else {
                for (int i = qMin(pt2.y(), m_ptSelEnd.y()); i <= qMax(pt2.y(), m_ptSelEnd.y()); i++) {
                    m_ansiLineList.at(i)->setChanges(qMin(pt2.x(), m_ptSelEnd.x()), pt1.x());
                }
            }
        }
    } else {

    }

    m_ptSelStart = pt1;
    m_ptSelEnd = pt2;
    // It seems QRect::normalized() not work correctly, at least in 4.3, we have to write it this way.
    m_rcSel.setTop(qMin(pt1.y(), pt2.y()));
    m_rcSel.setBottom(qMax(pt1.y(), pt2.y()));
    m_rcSel.setLeft(qMin(pt1.x(), pt2.x()));
    m_rcSel.setRight(qMax(pt1.x(), pt2.x()));
    return true;
}

void QAnsiBuffer::clearSelection()
{
    if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
        return;
    }
    
    if (m_bRect) {
        for (int i = m_rcSel.top(); i <= m_rcSel.bottom(); i++) {
            m_ansiLineList.at(i)->setChanges(m_rcSel.left(), m_rcSel.right());
        }
    } else {

    }
    m_ptSelStart = m_ptSelEnd = QPoint(-1, -1);
}

bool QAnsiBuffer::isSelected(const QPoint &pt) const
{
    if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
        return false;
    }

    if (m_bRect) {
        return pt.x() >= m_rcSel.left() && pt.x() <= m_rcSel.right()
                && pt.y() >= m_rcSel.top() && pt.y() <= m_rcSel.bottom();
    } else {
        return false;
    }
}

QString QAnsiBuffer::getSelectedText(bool bColor, const QString &strEscape) const
{
    if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
        return QString();
    }

    QString strRet = QString();
    if (m_bRect) {
        for (int i = m_rcSel.top(); i <= m_rcSel.bottom(); i++) {
            QString strLine = QString();
            if (bColor) {
                strLine = m_ansiLineList.at(i)->getAnsiText(m_rcSel.left(), m_rcSel.width(), strEscape);
            } else {
                strLine = m_ansiLineList.at(i)->getText(m_rcSel.left(), m_rcSel.width());
            }
            strRet += strLine;
            if (i != m_rcSel.bottom()) {
                /* To be telnet-compatible, use "\r\n" here */
                strRet += "\r\n";
            }
        }
    } else {

    }

    return strRet;
}

QByteArray QAnsiBuffer::getEncodedSelectedText(QTextCodec *pCodec, const QString &strEscape) const
{
    if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
        return QByteArray();
    }

    QByteArray arrRet = QByteArray();
    if (m_bRect) {
        for (int i = m_rcSel.top(); i <= m_rcSel.bottom(); i++) {
            QByteArray arrTemp = m_ansiLineList.at(i)->getEncodedAnsiText(pCodec, m_rcSel.left(), m_rcSel.width(), strEscape);
            arrRet += arrTemp;
            if (i != m_rcSel.bottom()) {
                /* To be telnet-compatible, use "\r\n" here */
                arrRet += "\r\n";
            }
        }
    } else {

    }

    return arrRet;
}

void QAnsiBuffer::setAttributeOfSelectedText(char chColor, char chEffect)
{
    if (m_ptSelStart == QPoint(-1, -1) || m_ptSelEnd == QPoint(-1, -1)) {
        return;
    }

    if (m_bRect) {
        for (int i = m_rcSel.top(); i <= m_rcSel.bottom(); i++) {
            QAnsiLine *line = m_ansiLineList.at(i);
            for (int j = 0; j < m_rcSel.width(); j++) {
                if (chColor != -1) {
                    line->setColor(m_rcSel.left()+j, chColor);
                }
                if (chEffect != -1) {
                    line->setEffect(m_rcSel.left()+j, chEffect);
                }
            }
        }
    } else {

    }
}

void QAnsiBuffer::scrollLines(int iStart, int iCount)
{
    if (iCount == 0) {
        return;
    }
    // scroll up
    if (iCount > 0) {
        for (int i = 0; i < iCount; i++) {
            delete m_ansiLineList.takeAt(iStart);
            m_ansiLineList.append(new QAnsiLine);
        }
    }
    if (iCount < 0) {
        for (int i = 0; i < -iCount; i++) {
            delete m_ansiLineList.takeAt(m_iRow-1);
            m_ansiLineList.insert(iStart, new QAnsiLine);
        }
    }
    // set changes
    for (int i = iStart; i < m_iRow; i++) {
        m_ansiLineList.at(i)->setChanges(-1, -1);
    }
}

void QAnsiBuffer::clearArea(int iX, int iY, int iWidth, int iHeight)
{
    Q_ASSERT(iX >= 0 && iX < m_iColumn);
    Q_ASSERT(iY >= 0 && iY < m_iRow);

    int iStartY = iY;
    int iEndY = iY + iHeight - 1;
    if (iEndY > m_iRow - 1) {
        iEndY = m_iRow - 1;
    }

    QAnsiLine *pLine = 0;
    for (int i = iStartY; i <= iEndY; i++) {
        pLine = m_ansiLineList.at(i);
        QString strSpace;
        if (iWidth == -1) {
            strSpace = QString(pLine->getLength() - iX, ' ');
        } else {
            strSpace = QString(iWidth, ' ');
        }
        pLine->setCurrentColor(m_chCurrentColor);
        pLine->setCurrentEffect(m_chCurrentEffect);
        pLine->replaceText(strSpace, iX);
    }
}


}  // namespace QAnsiEditor
